# encoding: utf-8

# Copyright 2008-2010 Natalia Bidart and Daniel Moisset
# $Id: server_test_lab1.py 299 2010-03-21 01:34:44Z dmoisset $

import unittest, client
import constants
import select, time, socket, os, os.path, logging

DATADIR='testdata'
TIMEOUT=3 # Una cantidad razonable de tiempo para esperar respuestas

class TestBase(unittest.TestCase):

    # Test environment...
    def setUp(self):
        os.system ('rm -rf %s' % DATADIR)
        os.mkdir (DATADIR)

    def tearDown(self):
        os.system ('rm -rf %s' % DATADIR)
        if hasattr(self, 'client'):
            if self.client.connected:
                # Deshabilitar el logging al desconectar
                # Dado que en algunos casos de prueba forzamos a que
                # nos desconecten de mala manera
                logging.getLogger().setLevel('CRITICAL')
                try:
                    self.client.close()
                except socket.error:
                    pass # Seguramente ya se desconecto del otro lado
                logging.getLogger().setLevel('WARNING')
            del self.client
        if hasattr(self, 'output_file'):
            if os.path.exists(self.output_file):
                os.remove(self.output_file)
            del self.output_file

    # Auxiliary functions
    def new_client(self):
        assert not hasattr(self, 'client')
        try:
            self.client = client.Client()
        except socket.error:
            self.fail ("No se pudo establecer conexión al server")
        return self.client


class TestHFTPServer(TestBase):

    # Tests
    def test_connect_and_quit(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.connect((constants.default_server, constants.default_port))
        except socket.error:
            self.fail ("No se pudo establecer conexión al server")
        s.send('quit\r\n')
        # Le damos TIMEOUT segundos para responder _algo_ y desconectar
        w, _, __ = select.select([s],[],[],TIMEOUT)
        self.assertEqual (w, [s], "Se envió quit, no hubo respuesta en %0.1f segundos" % TIMEOUT)
        # Medio segundo más par 
        start = time.clock()
        got = s.recv (1024)
        while got > 0 and time.clock() - start <= 0.5:
            r, w, e = select.select([s],[],[], 0.5)
            self.assertEqual (r, [s], "Luego de la respuesta de quit, la "
                                      "conexión se mantuvo activa por más "
                                      "de 0.5 segundos")
            got = s.recv (1024)
        # Se desconectó?
        self.assert_ (time > 0)

    def test_quit_answers_ok(self):
        c = self.new_client()
        c.close()
        self.assertEqual (c.status, constants.code_ok)

    def test_lookup(self):
        # Prepare data directory
        f = open(os.path.join (DATADIR, 'bar'), 'w').close()
        f = open(os.path.join (DATADIR, 'foo'), 'w').close()
        f = open(os.path.join (DATADIR, 'x'), 'w').close()
        c = self.new_client()
        files = sorted(c.file_lookup())
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (files, ['bar', 'foo', 'x']) # La lista de archivos es la correcta?

    def test_get_metadata (self):
        test_size = 123459
        f = open(os.path.join (DATADIR, 'bar'), 'w')
        f.write ('x'*test_size)
        f.close()
        c = self.new_client ()
        m = c.get_metadata ('bar')
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (m, test_size, "El tamaño reportado para el archivo no es el correcto")

    def test_get_metadata_empty (self):
        f = open(os.path.join (DATADIR, 'bar'), 'w').close()
        c = self.new_client ()
        m = c.get_metadata ('bar')
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (m, 0, "El tamaño reportado para el archivo no es el correcto")

    def test_get_full_slice (self):
        self.output_file = 'bar'
        test_data = 'The quick brown fox jumped over the lazy dog'
        f = open(os.path.join (DATADIR, self.output_file), 'w')
        f.write (test_data)
        f.close()
        c = self.new_client ()
        c.get_slice (self.output_file, 0, len(test_data))
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), test_data, "El contenido del archivo no es el correcto")

    def test_partial_slices (self):
        self.output_file = 'bar'
        test_data = 'a'*100+'b'*200+'c'*300
        f = open(os.path.join (DATADIR, self.output_file), 'w')
        f.write (test_data)
        f.close()
        c = self.new_client ()
        c.get_slice (self.output_file, 0, 100)
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), 'a'*100, "El contenido del archivo no es el correcto")
        c.get_slice (self.output_file, 100, 200)
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), 'b'*200, "El contenido del archivo no es el correcto")
        c.get_slice (self.output_file, 200, 200)
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), 'b'*100+'c'*100, "El contenido del archivo no es el correcto")
        c.get_slice (self.output_file, 500, 100)
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), 'c'*100, "El contenido del archivo no es el correcto")

class TestHFTPErrors(TestBase):

    def test_bad_eol (self):
        c = self.new_client()
        c.send('qui\nt\n')
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.bad_eol, "El servidor no contestó 100 ante un fin de línea erróneo")

    def test_bad_command (self):
        c = self.new_client()
        c.send('verdura')
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.invalid_command, "El servidor no contestó 200 ante un comando inválido")

    def test_bad_argument_count (self):
        c = self.new_client()
        c.send('quit passing extra arguments!')
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.invalid_arguments, "El servidor no contestó 201 ante una lista de argumentos muy larga")

    def test_bad_argument_count_2 (self):
        c = self.new_client()
        c.send('get_metadata') # No arguments
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.invalid_arguments, "El servidor no contestó 201 ante una lista de argumentos muy corta")

    def test_bad_argument_type (self):
        f = open(os.path.join (DATADIR, 'bar'), 'w')
        f.write ('data')
        f.close()
        c = self.new_client()
        c.send('get_slice bar x x') # Arguments should be integers
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.invalid_arguments, "El servidor no contestó 201 ante una lista de argumentos mal tipada (status=%d)" % status)

    def test_file_not_found (self):
        c = self.new_client()
        c.send('get_metadata does_not_exist')
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.file_not_found, "El servidor no contestó 202 ante un archivo inexistente")

class TestHFTPHard(TestBase):

    def test_command_in_pieces (self):
        c = self.new_client()
        for ch in 'quit\r\n':
            c.s.send(ch)
            os.system('sleep 1') # Despaciiiiiiiiiiito
        status, message = c.read_response_line(TIMEOUT)
        self.assertEqual (status, constants.code_ok, "El servidor no entendio un quit enviado de a un caracter por vez")

    def test_big_file (self):
        self.output_file = 'bar'
        f = open(os.path.join (DATADIR, self.output_file), 'w')
        for i in range (1,255):
            f.write (chr(i)*(2**17)) # 128KB
        f.close()
        c = self.new_client ()
        size = c.get_metadata(self.output_file)
        self.assertEqual (c.status, constants.code_ok)
        c.get_slice (self.output_file, 0, size)
        self.assertEqual (c.status, constants.code_ok)
        f = open(self.output_file)
        for i in range (1,255):
            s = f.read(2**17) # 128 KB
            self.assertEqual (s, chr(i)*(2**17), "El contenido del archivo no es el correcto")

    def test_big_filename (self):
        c = self.new_client()
        c.send('get_metadata '+'x'*(5 * 2**20),timeout=120) # Le damos 4 minutos a esto
        status, message = c.read_response_line(TIMEOUT*6) # Le damos un rato mas
        self.assertEqual (status, constants.file_not_found, "El servidor no contestó 202 ante un archivo inexistente con nombre muy largo (status=%d)" % status)

    def test_data_with_nulls (self):
        self.output_file = 'bar'
        test_data = 'x'*100+'\0'*100+'y'*100
        f = open(os.path.join (DATADIR, self.output_file), 'w')
        f.write (test_data)
        f.close()
        c = self.new_client ()
        c.get_slice (self.output_file, 0, len(test_data))
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (open(self.output_file).read(), test_data, "El contenido del archivo con NULs no es el correcto")

    def test_long_file_listing (self):
        # Prepare data directory
        correct_list = []
        for i in xrange (1000):
            filename = 'test_file%04d' % i
            f = open(os.path.join (DATADIR, filename), 'w').close()
            correct_list.append(filename)
        c = self.new_client()
        files = sorted(c.file_lookup())
        self.assertEqual (c.status, constants.code_ok)
        self.assertEqual (files, correct_list, "La lista de 1000 archivos no es la correcta")

def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestHFTPServer))
    suite.addTest(unittest.makeSuite(TestHFTPErrors))
    suite.addTest(unittest.makeSuite(TestHFTPHard))
    return suite


def main():
    import optparse
    global DATADIR
    parser = optparse.OptionParser()
    parser.add_option('-d', '--datadir', help="Directorio donde genera los datos; CUIDADO: CORRER LOS TESTS *BORRA* LOS DATOS EN ESTE DIRECTORIO", default=DATADIR)
    options, args = parser.parse_args()
    DATADIR = options.datadir
    # Correr tests
    unittest.main()

if __name__ == '__main__':
    main()

